﻿#region License statement
// NJamb, a specification and delta-specification DSL
// Copyright (c) 2010-2011, Mark Knell
// Published under the MIT License; all other rights reserved
#endregion

#region using...
using System;
using System.Linq.Expressions;
using NJamb.Parser.SpecificationBuilders;
using NJamb.Parser.SpecificationBuilders.SubjectHas;
using NJamb.Parser.SpecificationBuilders.SubjectIs;
using NJamb.Specifications;
using Stile.Patterns.SelfDescribingPredicates;
using Stile.Patterns.SelfDescribingPredicates.Evaluators;
using Stile.Types.Expressions;
#endregion

namespace NJamb.Parser.Subjects
{
    public abstract class Subject<TSubject, TCollaborating, TSpecifies> : ISubject<TSubject, TCollaborating, TSpecifies>,
        ICollaboratingSubject
        where TCollaborating : class, ICollaboratingSubject
        where TSpecifies : class, ISpecification<TSubject>
    {
        protected Subject(Lazy<string> lazyDescription)
        {
            LazyDescription = lazyDescription;
        }

        public string Description
        {
            get
            {
                string value = LazyDescription.Value;
                return value;
            }
        }
        public Lazy<string> LazyDescription { get; private set; }
        public abstract TCollaborating Xray { get; }

        //public TSpecifies DoesNotThrow()
        //{
        //    throw new NotImplementedException();
        //}

        public TSpecifies Throws<TException>() where TException : Exception
        {
            var evaluator = new ExceptionEvaluator<TSubject, TException>();

            Func<Exception, TSubject, IEvaluation<TSubject>> exceptionFilter =
                SpecificationBuilderExtensions.MakeExceptionFilter<TSubject, TException>(LazyDescription);

            return Specify(evaluator, exceptionFilter);
        }

        public abstract TSpecifies Specify(ISelfDescribingPredicate<TSubject> selfDescribingPredicate,
            Func<Exception, TSubject, IEvaluation<TSubject>> exceptionFilter = null);
    }

    public abstract class Subject<TSubject, THas, TIs, TSpecifies> :
        Subject<TSubject, ICollaboratingSubject<TSubject, TSpecifies>, TSpecifies>,
        ISubject<TSubject, THas, TIs, TSpecifies>,
        ICollaboratingSubject<TSubject, TSpecifies>
        where THas : class, IFluentHas<TSubject, TSpecifies>
        where TIs : class, IFluentIs<TSubject, TSpecifies>
        where TSpecifies : class, ISpecification<TSubject>
    {
        private readonly Func<TSubject> _getter;
        private readonly Lazy<THas> _lazyHas;
        private readonly Lazy<TIs> _lazyIs;

        protected Subject(Func<TSubject> getter, Lazy<string> lazyDescription)
            : base(lazyDescription)
        {
            _getter = getter;
            _lazyHas = new Lazy<THas>(MakeHas);
            _lazyIs = new Lazy<TIs>(MakeIs);
        }

        public THas Has
        {
            get
            {
                THas value = _lazyHas.Value;
                return value;
            }
        }
        public TIs Is
        {
            get
            {
                TIs value = _lazyIs.Value;
                return value;
            }
        }
        public override ICollaboratingSubject<TSubject, TSpecifies> Xray
        {
            get { return this; }
        }

        public TSubject Get()
        {
            TSubject subject = _getter.Invoke();
            return subject;
        }

        public TSpecifies Satisfies(ISelfDescribingPredicate<TSubject> selfDescribingPredicate)
        {
            return Specify(selfDescribingPredicate);
        }

        public TSpecifies Satisfies(Expression<Func<TSubject, bool>> lambdaPredicate)
        {
            VerbTensePair verbTensePair = VerbTensePair.WouldSatisfyButWas;
            var evaluator =
                new Evaluator<TSubject>(new ExpectedClause<TSubject>(verbTensePair, subject => lambdaPredicate.ToDebugString()),
                    new NoOpActualClause<TSubject>(),
                    lambdaPredicate.Compile());
            return Satisfies(evaluator);
        }

        protected abstract THas MakeHas();
        protected abstract TIs MakeIs();
    }
}
